namespace SkyDean.FareLiz.WinForm.Components.Controls.DatePicker.Design
{
    using System;
    using System.ComponentModel;
    using System.Linq;
    using System.Windows.Forms;

    /// <summary>Class for displaying a flags enumeration in the designer.</summary>
    /// <remarks>From CodeProject : http://www.codeproject.com/KB/edit/flagenumeditor.aspx</remarks>
    [ToolboxItem(false)]
    internal class FlagCheckedListBox : CheckedListBox
    {
        #region constructor

        /// <summary>Initializes a new instance of the <see cref="FlagCheckedListBox" /> class.</summary>
        public FlagCheckedListBox()
        {
            this.CheckOnClick = true;
        }

        #endregion

        #region Properties

        /// <summary>Gets or sets the enum value.</summary>
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public Enum EnumValue
        {
            get
            {
                var e = Enum.ToObject(this.enumType, this.GetCurrentValue());

                return (Enum)e;
            }

            set
            {
                this.Items.Clear();

                // Store the current enum value
                this.enumValue = value;

                // Store enum type
                this.enumType = value.GetType();

                // Add items for enum members
                this.FillEnumMembers();

                // Check/uncheck items depending on enum value
                this.ApplyEnumValue();
            }
        }

        #endregion

        #region nested classes

        /// <summary>Class that represents an item in the <see cref="FlagCheckedListBox" />.</summary>
        /// <remarks>From CodeProject.</remarks>
        private class FlagCheckedListBoxItem
        {
            /// <summary>
            /// Initializes a new instance of the <see cref="FlagCheckedListBoxItem"/> class.
            /// </summary>
            /// <param name="value">
            /// The value of the item.
            /// </param>
            /// <param name="caption">
            /// The caption of the item.
            /// </param>
            public FlagCheckedListBoxItem(int value, string caption)
            {
                this.Value = value;
                this.Caption = caption;
            }

            /// <summary>Gets or sets the value of the item.</summary>
            public int Value { get; set; }

            /// <summary>Gets or sets the caption of the item.</summary>
            public string Caption { get; set; }

            /// <summary>Returns a string which represents the object.</summary>
            /// <returns>A string which represents the object.</returns>
            public override string ToString()
            {
                return this.Caption;
            }
        }

        #endregion

        #region Fields

        /// <summary>Indicates whether currently updating the check states.</summary>
        private bool isUpdatingCheckStates;

        /// <summary>The type of the enum.</summary>
        private Type enumType;

        /// <summary>The enum value.</summary>
        private Enum enumValue;

        #endregion

        #region methods

        /// <summary>
        /// Handles the raising of the <see cref="CheckedListBox.ItemCheck"/> event.
        /// </summary>
        /// <param name="e">
        /// An <see cref="ItemCheckEventArgs"/> instance that contains the event data.
        /// </param>
        protected override void OnItemCheck(ItemCheckEventArgs e)
        {
            base.OnItemCheck(e);

            if (this.isUpdatingCheckStates)
            {
                return;
            }

            // Get the checked/unchecked item
            var item = this.Items[e.Index] as FlagCheckedListBoxItem;

            // Update other items
            this.UpdateCheckedItems(item, e.NewValue);
        }

        /// <summary>
        /// Adds an integer value and its associated description.
        /// </summary>
        /// <param name="v">
        /// The value to add.
        /// </param>
        /// <param name="c">
        /// The description string to add.
        /// </param>
        private void Add(int v, string c)
        {
            var item = new FlagCheckedListBoxItem(v, c);

            this.Items.Add(item);
        }

        /// <summary>Gets the current bit value corresponding to all checked items.</summary>
        /// <returns>The bit value.</returns>
        private int GetCurrentValue()
        {
            return
                (from object t in this.Items select t as FlagCheckedListBoxItem).Where((item, i) => this.GetItemChecked(i) && item != null)
                    .Aggregate(0, (current, item) => current | item.Value);
        }

        /// <summary>
        /// Checks or unchecks items depending on the given bit value.
        /// </summary>
        /// <param name="value">
        /// The bit value.
        /// </param>
        private void UpdateCheckedItems(int value)
        {
            this.isUpdatingCheckStates = true;

            // Iterate over all items
            for (var i = 0; i < this.Items.Count; i++)
            {
                var item = this.Items[i] as FlagCheckedListBoxItem;

                if (item == null)
                {
                    continue;
                }

                if (item.Value == 0)
                {
                    this.SetItemChecked(i, value == 0);
                }
                else
                {
                    this.SetItemChecked(i, (item.Value & value) == item.Value && item.Value != 0);
                }
            }

            this.isUpdatingCheckStates = false;
        }

        /// <summary>
        /// Updates items in the checklistbox.
        /// </summary>
        /// <param name="composite">
        /// The item that was checked/unchecked.
        /// </param>
        /// <param name="cs">
        /// The check RequestState of that item.
        /// </param>
        private void UpdateCheckedItems(FlagCheckedListBoxItem composite, CheckState cs)
        {
            if (composite == null)
            {
                return;
            }

            // If the value of the item is 0, call directly.
            if (composite.Value == 0)
            {
                this.UpdateCheckedItems(0);
            }

            // Get the total value of all checked items
            var sum =
                (from object t in this.Items select t as FlagCheckedListBoxItem).Where((item, i) => item != null && this.GetItemChecked(i))
                    .Aggregate(0, (current, item) => current | item.Value);

            // If the item has been unchecked, remove its bits from the sum
            if (cs == CheckState.Unchecked)
            {
                sum = sum & (~composite.Value);
            }
            else
            {
                // If the item has been checked, combine its bits with the sum
                sum |= composite.Value;
            }

            // Update all items in the checklistbox based on the final bit value
            this.UpdateCheckedItems(sum);
        }

        /// <summary>Adds items to the checklistbox based on the members of the enum.</summary>
        private void FillEnumMembers()
        {
            foreach (var name in Enum.GetNames(this.enumType))
            {
                var val = Enum.Parse(this.enumType, name);

                var intVal = (int)Convert.ChangeType(val, typeof(int));

                this.Add(intVal, name);
            }

            this.Height = this.GetItemHeight(0) * 8;
        }

        /// <summary>Checks/unchecks items based on the current value of the enum variable.</summary>
        private void ApplyEnumValue()
        {
            var intVal = (int)Convert.ChangeType(this.enumValue, typeof(int));

            this.UpdateCheckedItems(intVal);
        }

        #endregion
    }
}